--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit cadf6fe66f5689cd228d2eda6be81e452a6d68ff
Parents : 0889cd0
Author : Sudo-Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-01-01T17:34:47-06:00
feat(voicemail): refactor VoicemailManager to use config for settings, add greeting conversion and removal methods
Changes
1 files changed, 63 insertions(+), 29 deletions(-)
Diff
diff --git a/meshchatx/src/backend/voicemail_manager.py b/meshchatx/src/backend/voicemail_manager.py
index b8911b9e..ecf12c3d 100644
--- a/meshchatx/src/backend/voicemail_manager.py
+++ b/meshchatx/src/backend/voicemail_manager.py
@@ -14,8 +14,9 @@ from LXST.Sources import OpusFileSource
class VoicemailManager:
- def __init__(self, db, telephone_manager, storage_dir):
+ def __init__(self, db, config, telephone_manager, storage_dir):
self.db = db
+ self.config = config
self.telephone_manager = telephone_manager
self.storage_dir = os.path.join(storage_dir, "voicemails")
self.greetings_dir = os.path.join(self.storage_dir, "greetings")
@@ -50,6 +51,10 @@ class VoicemailManager:
else:
RNS.log("Voicemail: ffmpeg not found", RNS.LOG_ERROR)
+ def get_name_for_identity_hash(self, identity_hash):
+ """Default implementation, should be patched by ReticulumMeshChat"""
+ return None
+
def _find_espeak(self):
# Try standard name first
path = shutil.which("espeak-ng")
@@ -103,53 +108,82 @@ class VoicemailManager:
# espeak-ng to WAV
subprocess.run([self.espeak_path, "-w", wav_path, text], check=True)
- # ffmpeg to Opus
- if os.path.exists(opus_path):
- os.remove(opus_path)
-
- subprocess.run(
- [
- self.ffmpeg_path,
- "-i",
- wav_path,
- "-c:a",
- "libopus",
- "-b:a",
- "16k",
- "-vbr",
- "on",
- opus_path,
- ],
- check=True,
- )
-
- return opus_path
+ # Convert WAV to Opus
+ return self.convert_to_greeting(wav_path)
finally:
if os.path.exists(wav_path):
os.remove(wav_path)
+ def convert_to_greeting(self, input_path):
+ if not self.has_ffmpeg:
+ msg = "ffmpeg is required for audio conversion"
+ raise RuntimeError(msg)
+
+ opus_path = os.path.join(self.greetings_dir, "greeting.opus")
+
+ if os.path.exists(opus_path):
+ os.remove(opus_path)
+
+ subprocess.run(
+ [
+ self.ffmpeg_path,
+ "-i",
+ input_path,
+ "-c:a",
+ "libopus",
+ "-b:a",
+ "16k",
+ "-vbr",
+ "on",
+ opus_path,
+ ],
+ check=True,
+ )
+
+ return opus_path
+
+ def remove_greeting(self):
+ opus_path = os.path.join(self.greetings_dir, "greeting.opus")
+ if os.path.exists(opus_path):
+ os.remove(opus_path)
+ return True
+
def handle_incoming_call(self, caller_identity):
- if not self.db.config.voicemail_enabled.get():
+ RNS.log(f"Voicemail: handle_incoming_call from {RNS.prettyhexrep(caller_identity.hash)}", RNS.LOG_DEBUG)
+ if not self.config.voicemail_enabled.get():
+ RNS.log("Voicemail: Voicemail is disabled", RNS.LOG_DEBUG)
return
- delay = self.db.config.voicemail_auto_answer_delay_seconds.get()
+ delay = self.config.voicemail_auto_answer_delay_seconds.get()
+ RNS.log(f"Voicemail: Will auto-answer in {delay} seconds", RNS.LOG_DEBUG)
def voicemail_job():
+ RNS.log(f"Voicemail: Auto-answer timer started for {RNS.prettyhexrep(caller_identity.hash)}", RNS.LOG_DEBUG)
time.sleep(delay)
# Check if still ringing and no other active call
telephone = self.telephone_manager.telephone
+ if not telephone:
+ RNS.log("Voicemail: No telephone object", RNS.LOG_ERROR)
+ return
+
+ RNS.log(f"Voicemail: Checking status. Call status: {telephone.call_status}, Active call: {telephone.active_call}", RNS.LOG_DEBUG)
+
if (
telephone
and telephone.active_call
- and telephone.active_call.get_remote_identity() == caller_identity
+ and telephone.active_call.get_remote_identity().hash == caller_identity.hash
and telephone.call_status == LXST.Signalling.STATUS_RINGING
):
RNS.log(
f"Auto-answering call from {RNS.prettyhexrep(caller_identity.hash)} for voicemail",
- RNS.LOG_DEBUG,
+ RNS.LOG_INFO,
)
self.start_voicemail_session(caller_identity)
+ else:
+ RNS.log("Voicemail: Auto-answer conditions not met after delay", RNS.LOG_DEBUG)
+ if telephone.active_call:
+ RNS.log(f"Voicemail: Active call remote: {RNS.prettyhexrep(telephone.active_call.get_remote_identity().hash)}", RNS.LOG_DEBUG)
threading.Thread(target=voicemail_job, daemon=True).start()
@@ -170,7 +204,7 @@ class VoicemailManager:
greeting_path = os.path.join(self.greetings_dir, "greeting.opus")
if not os.path.exists(greeting_path):
# Fallback if no greeting generated yet
- self.generate_greeting(self.db.config.voicemail_greeting.get())
+ self.generate_greeting(self.config.voicemail_greeting.get())
def session_job():
try:
@@ -206,7 +240,7 @@ class VoicemailManager:
self.start_recording(caller_identity)
# 4. Wait for max recording time or hangup
- max_time = self.db.config.voicemail_max_recording_seconds.get()
+ max_time = self.config.voicemail_max_recording_seconds.get()
start_wait = time.time()
while self.is_recording and (time.time() - start_wait < max_time):
time.sleep(0.5)
@@ -271,7 +305,7 @@ class VoicemailManager:
# Save to database if long enough
if duration >= 1:
- remote_name = self.telephone_manager.get_name_for_identity_hash(
+ remote_name = self.get_name_for_identity_hash(
self.recording_remote_identity.hash.hex()
)
self.db.voicemails.add_voicemail(
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────